package cx3k.scheduler.wrappers;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;

import cx3k.dataaccess.StandardDataAccessService;
import cx3k.dataaccess.exceptions.DataAccessException;
import cx3k.dataaccess.search.Searcher;
import cx3k.dataaccess.search.Searcher.SearchField;
import cx3k.scheduler.objects.CompletedCourse;
import cx3k.scheduler.objects.Course;
import cx3k.scheduler.objects.Section;
import cx3k.scheduler.objects.Semester;

/**
 * The Course Wrapper object is designed to wrap a Course object, which 
 * presumably has been loaded from persistent storage.  This provides the 
 * additional functionality that would be improper to include in the 
 * Course object itself, because of the nature of the persistent objects.
 * <p>
 * The Wrapper is designed to encapsulate the Course object, providing modified
 * access to its members.  Mainly, the Wrapper provides the ability to 
 * access a list of prerequisites as Course objects, rather than as 
 * Course ID values.  
 * </p>
 * <p>
 * Because of this functionality, this Wrapper is dependent on the 
 * StandardDataAccessService, which provides this required functionality.
 * </p>
 * 
 * @see 	cx3k.dataaccess.StandardDataAccessService
 * @author 	Chris Thierer
 */
public class CourseWrapper implements Comparable<CourseWrapper> {

	/**
	 * Constant value representing a value of no credits for a course.
	 */
	public static final int NO_CREDITS = 0;
	
	/**
	 * Constant value representing the divider that splits the Course IDs,
	 * which are stored as String in the database as a list.  
	 */
	public static final String PREREQ_DIVIDER = ",";
	
	/**
	 * Log information to specified location.
	 */
	private static Logger logger = Logger.getLogger(CourseWrapper.class);
	
	/**
	 * @return	The logger for this class.
	 */
	private static Logger getLogger(){
		return logger;
	}
	
	/**
	 * Reference to an instance of Course being wrapped by this class.  This
	 * reference cannot be directly accessed from outside of this class.
	 */
	private Course course;
	
	/**
	 * The number of credits for the wrapped Course object, stored as an 
	 * integer value.  This value represents the value stored in the Course
	 * object during initialization; if the value of the referenced object 
	 * changes, the number of credits will not be updated.
	 */
	private int credits;
	
	
	public static void main(String[] args){
		
		Semester semester = new Semester();
		semester.setSemesterId(1000007);
		
		List<CourseWrapper> wrappers = getCourseWrappers("CMSC", "411", semester);
		
		for(CourseWrapper wrapper : wrappers){
			System.out.println(wrapper);
		}
	}
	
	/**
	 * Get the Course Wrappers for a corresponding course, identified by the
	 * semester, degree program, and course number.
	 * @param degreeProgram The degree program of the desired course.
	 * @param courseNumber The course number of the desired course.
	 * @param semester The semester to which the course belongs.
	 * @return A list of Course Wrappers that match the specifications, or an 
	 * 			empty list.
	 */
	@SuppressWarnings("unchecked")
	public static List<CourseWrapper> getCourseWrappers(String degreeProgram, 
			String courseNumber, Semester semester){
		List<CourseWrapper> courses = null;
		List<?> results = null;
		
		Searcher searcher = new Searcher(Course.class);
		
		searcher.searchForExactValue(SearchField.DISCIPLINE, degreeProgram);
		searcher.searchForExactValue(SearchField.LEVEL, courseNumber);
		if(semester.getSemesterId() > 0){
			searcher.searchForExactValue(SearchField.SEMESTER_ID, ((Integer) semester.getSemesterId()).toString());
		} else {
			searcher.searchForExactValue(SearchField.SESSION_TYPE, semester.getSessionType());
			searcher.searchForExactValue(SearchField.SEMESTER_YR, ((Integer) semester.getYear()).toString());
		}
		
		results = searcher.execute();
		courses = new ArrayList(results.size());
		
		Iterator<?> iterator = results.iterator();
		
		while(iterator.hasNext()){
			Object[] obj = (Object[]) iterator.next();
			
			Course course = (Course) obj[0];
			
			courses.add(new CourseWrapper(course));
		}

		return courses;
	}
	
	/**
	 * Initialize the wrapper to contain a reference to the Course object 
	 * passed in.  This does not perform a deep copy on the object.
	 * @param 	course Reference to the course to be wrapped by this instance
	 * 			of CourseWrapper.
	 */
	public CourseWrapper(Course course){
		setCourse(course);
		setCredits(calcCredits(getCourse().getCredits()));
	}
	
	public CourseWrapper(int courseId){
		try {
			setCourse(StandardDataAccessService.getStandardDataAccessService().getCourse(courseId));
		} catch (DataAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Convert a string representing credits, and convert it to an integer
	 * value.  This method is designed to handle the scenario that the 
	 * number of credits is variable, i.e "3-6", which would not parse into
	 * an integer.  In this case, only the first digit encountered will be 
	 * added to the credits; the values after the first non-digit encountered
	 * will be ignored.  This would mean that a String value that did not 
	 * start with a digit, i.e. "TBD", would not be parsed.
	 * @param 	credits	The String representing the credits to be parsed into
	 * 			an integer.
	 * @return	The integer representation of the String value; or, if the 
	 * 			String does not parse cleanly, the first full integer value 
	 * 			encountered.  In the event that the String value does not start
	 * 			with a digit, NO_CREDITS will be returned.
	 */
	private int calcCredits(String credits){
		
		// trim the string
		credits = credits.trim();
		
		// the final converted value, default to no credits
		int converted = NO_CREDITS;
		
		try{
			// try to parse the string to an integer directly
			converted = Integer.parseInt(credits);
			
		} catch(NumberFormatException e){
				
			int count = 0;								// counter
			StringBuffer number = new StringBuffer();	// digits off the string
			boolean cont = true;						// flag to continue 
			
			// go through each letter in the string until non-digit is hit
			while(count < credits.length() - 1 && cont){
				try{
					// try to parse the string value to an integer
					number.append(Integer.parseInt(credits.substring(count, ++count)));
				} catch(NumberFormatException e2){ 
					// the value was not an integer, stop the loop
					cont = false;
				}
			}
			
			// check if any digits were converted
			if(number.length() > 0){
				// store digits as an integer
				converted = Integer.parseInt(number.toString());
			}
		}
		
		// return the result
		return converted;
		
	}
	
	/**
	 * Generate a String of Course IDs for the List of courses passed in.
	 * @param 	courses The List of courses.
	 * @return	A string of Course IDs, separated by PREREQ_DIVIDER constant.
	 */
	private String generateCourseIds(List<CourseWrapper> courses){
		String ret = "";
		
		for(CourseWrapper course : courses){
			ret += course.getCourseId() + PREREQ_DIVIDER;
		}
		
		return ret;
	}
	
	/**
	 * @return 	Reference to the Set of CompletedCourses associated with this
	 * 			Course.
	 */
	protected Set<CompletedCourse> getCompletedCourses(){
		return getCourse().getCompletedCourses();
	}
	
	/**
	 * @return	The reference to the Course being wrapped.
	 */
	public Course getCourse(){
		return course;
	}
	
	/**
	 * @return	The course ID value associated with the wrapped Course.  
	 */
	public int getCourseId(){
		return getCourse().getCourseId();
	}
	
	/**
	 * @return	The course number associated with the wrapped Course 
	 * 			object.
	 */
	public String getCourseNum(){
		return getCourse().getCourseNum();
	}
	
	/**
	 * @return 	The number of credits associated with the wrapped Course 
	 * 			object.
	 */
	public int getCredits(){
		return credits;
	}
	
	/**
	 * A degree program is the 4-letter String that is associated with the 
	 * department to which a course belongs.  For example, "CMSC" represents 
	 * the Computer Science degree program.  The degree program value stored
	 * here should match the program value associated with Majors and Minors.  
	 * @return	The four-letter String representing the degree program to which
	 * 			this course belongs.  
	 */
	public String getDegreeProgram(){
		return getCourse().getDegreeProgram();
	}
	
	/**
	 * @return	The description of the course.
	 */
	public String getDescription(){
		return getCourse().getDescription();
	}
	
	/**
	 * @return	The difficulty level of the course.
	 */
	public int getDifficulty(){
		return getCourse().getDifficulty().intValue();
	}
		
	
	/**
	 * @return	Set of Sections belonging to this course.
	 */
	public Set<Section> getSections(){
		return getCourse().getSections();
	}
	
	/**
	 * @return	Reference to the semester which this Course is a part of.
	 */
	public Semester getSemester(){
		return getCourse().getSemester();
	}
	
	
	
	/**
	 * Convert an array of Course IDs into initialized Course objects.
	 * @param 	courseIds The array of Course IDs.
	 * @return	A List of Course objects
	 */
	private List<CourseWrapper> loadCourses(String[] courseIds) {
		
		getLogger().trace("loadCourses(courseIds.length = " + courseIds.length + 
						  ") invoked: Loading courses from storage.");
		
		List<CourseWrapper> courses = new ArrayList<CourseWrapper>(courseIds.length);
		
		Course loaded;		// the loaded course from storage
		int courseId;		// the parsed course ID from the string
		
		// need to catch if data access service is not available
		try{
			
			// go through the array of course IDs
			for(int i = 0; i < courseIds.length; i++){
				
				// need to catch if the string is not a valid course ID (int)
				try{
					
					// parse the course ID to an integer
					courseId = Integer.parseInt(courseIds[i]);
					
					// load the Course from persistent storage
					loaded = StandardDataAccessService.getStandardDataAccessService().getCourse(courseId);
					
					// check if an object was loaded
					if(loaded != null){
						
						// object loaded, add to list
						courses.add(new CourseWrapper(loaded));
						
						getLogger().trace("Added course \"" + 
										  loaded.getDegreeProgram() + ": " + 
										  loaded.getCourseNum() + 
										  "\" to the list of courses.");
					} else {
					
						// no object was loaded, indicates invalid ID
						getLogger().warn("The course ID " + courseIds[i] + 
										 " did not map to an object from the " +
										 "database, and could not be loaded.");
					}
					
				} catch(NumberFormatException e){
					
					// could not parse the course ID, move not next element
					getLogger().warn("The course ID " + courseIds[i] + 
									 " could not be parsed to an integer, and so " +
									 "the course could not be loaded from the database: " + 
									 e.getMessage());
				}
	 		}
		} catch(DataAccessException ex){
			
			// could not access persistent storage, end
			getLogger().warn("Problem loading the Data Access Service (\"" + 
							 ex.getMessage() + 
							 "\"); could not load the Course objects from storage.");	
		}
		
		getLogger().trace("loadCourses(String[] courseIds) returning a List of size " + 
					  	  courses.size() + ".");
		
		return courses;
	}
	
	/**
	 * @param 	completedCourses Reference to a new Set of completed courses 
	 * 			for this Course.  This overrides the existing Set.
	 */
	protected void setCompletedCourses(Set<CompletedCourse> completedCourses){
		getCourse().setCompletedCourses(completedCourses);
	}
	
	/**
	 * @param 	courseId Value for the course ID associated with this course.
	 * 			This value should not be changed.
	 */
	protected void setCourseId(int courseId){
		getCourse().setCourseId(courseId);
	}
	
	/**
	 * @param 	courseNum Value for the course number associated with the 
	 * 			course.
	 */
	public void setCourseNum(String courseNum){
		getCourse().setCourseNum(courseNum);
	}
	
	/**
	 * @param 	credits Value for the credits associated with this course.
	 */
	public void setCredits(String credits){
		getCourse().setCredits(credits);
	}
	
	/**
	 * @param 	degreeProgram Value for the degree program associated with 
	 * 			this course.
	 */
	public void setDegreeProgram(String degreeProgram){
		getCourse().setDegreeProgram(degreeProgram);
	}
	
	/**
	 * @param 	description Value for the description for this course.
	 */
	public void setDescription(String description){
		getCourse().setDescription(description);
	}
	
	/**
	 * @param 	difficulty Value for the difficulty for this course.
	 */
	public void setDifficulty(int difficulty){
		getCourse().setDifficulty(difficulty);
	}
	
	/**
	 * @param 	sections Set of sections belonging to this course.
	 */
	public void setSection(Set<Section> sections){
		getCourse().setSections(sections);
	}
	
	/**
	 * @param  	semester The semester to which this course belongs.
	 */
	public void setSemeseter(Semester semester){
		getCourse().setSemester(semester);
	}
	
	/**
	 * Set the course instance variable to reference the instance of Course 
	 * passed in as a parameter.
	 * @param	course Reference to the course.
	 */
	private void setCourse(Course course){
		this.course = course;
	}
	
	/**
	 * Set the credits instance variable.
	 * @param 	credits The new value for the credits instance variable.  
	 */
	private void setCredits(int credits){
		this.credits = credits;
	}

	@Override
	public String toString(){
		return getDegreeProgram() + " " + getCourseNum();
	}
	
	@Override
	public int compareTo(CourseWrapper arg0) {
		// TODO Auto-generated method stub
		return 0;
	}
}
